<?php


namespace StudyBuddy\Controllers;

use \StudyBuddy\WebPage;
use \StudyBuddy\Request;
use \StudyBuddy\Responder;
use \StudyBuddy\Question;


/**
 * Controller for processing the 'Accept as best answer'
 * vote
 *
 * @author admin
 *
 */
class Accept extends WebPage
{

	protected $aRequired = array('aid');

	protected $aOldAnswer;

	/**
	 * 
	 * Question object
	 * @var Object Question for which answer is accepted
	 */
	protected $oQuestion;

	/**
	 * Answer object
	 * 
	 * @var Answer object
	 */
	protected $oAnswer;

	protected $membersOnly = true;

	protected $permission = 'accept';

	protected function main(){
		/**
		 * Init cache so it can listen to event
		 * Does not have to be set as instance variable
		 * because its constructor attaches itself to
		 * Dispatcher.
		 */
		$oCache = $this->oRegistry->Cache;
		$this->oRegistry->registerObservers('INPUT_FILTERS');
		//try{
		$this->checkVoteHack()
		->getAnswer()
		->getQuestion()
		->checkViewer()
		->postOnBefore()
		->updateQuestion()
		->updateUser()
		->makeActionCount('answer_accepted_total')
		->postEvent();
		//} catch(\Exception $e){
		//	d('Accept not counted due to exception: '.$e->getMessage().' in '.$e->getFile().' line: '.$e->getLine());
		//}

		$this->redirect();
	}



	/**
	 * Post onAcceptAnswer event
	 *
	 * @return object $this
	 */
	protected function postEvent(){
		$this->oRegistry->Dispatcher->post($this->oQuestion, 'onAcceptAnswer', array('answer' => $this->oAnswer));

		return $this;
	}


	/**
	 *
	 * post onBeforeAcceptAnswer event
	 * and if notification is cancelled by observer
	 * then throw Exception
	 * @throws \StudyBuddy\Exception
	 * @return object $this
	 */
	protected function postOnBefore(){
		$notification = $this->oRegistry->Dispatcher->post($this->oQuestion, 'onBeforeAcceptAnswer', array('answer' => $this->oAnswer));
		if($notification->isNotificationCancelled()){
			d('notification onBeforeAcceptAnswer cancelled');
			/**
			 * Set Answer object as saved
			 * so it will not be auto-saved
			 * by its own __destruct()
			 */
			$this->oAnswer->setSaved();
			throw new \StudyBuddy\Exception('This feature is unavailable at this time');
		}

		return $this;
	}


	/**
	 * Check VOTE_HACKS table
	 * if accept action comes from ip
	 * address or user id who been flagged
	 * for vote hack then don't proceed
	 * any further
	 *
	 * @todo move this to external class and make
	 * this method static, accepting only Registry
	 *
	 * @throws Exception if accept action comes from
	 * someone who's been flagged in the past 2 days
	 *
	 * @return object $this
	 */
	protected function checkVoteHack(){

		if(!$this->oRegistry->Viewer->isModerator()){
			$timeOffset = time() - 172800; // 2 days

			$cur = $this->oRegistry->Mongo->VOTE_HACKS->find(array('i_ts' => array('$gt' => $timeOffset)));

			if($cur && $cur->count(true)  > 0){
				$ip = Request::getIP();
				$uid = $this->oRegistry->Viewer->getUid();

				foreach($cur as $aRec){
					if($ip === $aRec['ip'] || $uid == $uid){
						throw new \StudyBuddy\Exception('This action is disabled at this time');
					}
				}
			}
		}

		return $this;
	}


	/**
	 * Get record for the selected answer
	 * and create the $this->oAnswer Answer object
	 *
	 * @throws \StudyBuddy\Exception if unable to find record
	 * in ANSWERS collection
	 *
	 * @return object $this
	 */
	protected function getAnswer(){
		$aAnswer = $this->oRegistry->Mongo->ANSWERS->findOne(array('_id' => $this->oRequest['aid']));
		d('$aAnswer: '.print_r($aAnswer, 1));

		if(empty($aAnswer)){
			throw new \StudyBuddy\Exception('Answer not found by id: '.$this->oRequest['aid']);
		}

		$this->oAnswer = new \StudyBuddy\Answer($this->oRegistry, $aAnswer);

		return $this;
	}


	/**
	 * Get data for a question for which
	 * the answer is being accepted
	 * and create object $this->oQuestion
	 *
	 * We need object instead of just array because
	 * object has getUrl() method which we will need to redirect
	 * Also object has an auto-save changes so we can just make changes
	 * to the object and don't worry about explicitely saving to Mongo
	 *
	 * @throws InvalidArgumentException
	 * @throws Exception
	 */
	protected function getQuestion(){
		$aQuestion = $this->oRegistry->Mongo->QUESTIONS->findOne(array('_id' => $this->oAnswer->getQuestionId()));

		d('$aQuestion: '.print_r($aQuestion, 1));

		if(empty($aQuestion)){
			throw new \StudyBuddy\Exception('Question not found for this answer: '.$this->oRequest['aid']);
		}

		$this->oQuestion = new Question($this->oRegistry, $aQuestion);
		d('cp');

		return $this;
	}


	/**
	 * Check ownership
	 * If Viewer is not the owner of the question
	 * then this is some type of a vote hack
	 * We should record it
	 *
	 * This does not apply to moderators as moderators
	 * can also accept the best answer
	 * 
	 * @throws StudyBuddy\Exception if accept action
	 * came from someone other than question owner or moderator
	 *
	 * @return object $this
	 */
	protected function checkViewer(){
		$ownerID = $this->oQuestion->getOwnerId();
		if(!$this->oRegistry->Viewer->isModerator()){
			if($ownerID != $this->oRegistry->Viewer->getUid()){
				d('cp voting for someone else question');
				/**
				 * Post onAcceptHack event
				 */
				$this->oRegistry->Dispatcher->post($this->oQuestion, 'onAcceptHack');
				
				$this->recordVoteHack();

				throw new \StudyBuddy\Exception('You can only accept answer for your own question');

			}
		}

		return $this;
	}


	/**
	 * Update the value of i_sel_and and i_lm_ts
	 * in the QUESTIONS collection for this question
	 *
	 * @throws Exception is the same answer is already the accepted
	 * one. This is just a safety check, should not happend usually
	 *
	 * @return object $this
	 */
	protected function updateQuestion(){
		$ansID = $this->oQuestion['i_sel_ans'];
		d('$ansID: '.$ansID);

		if(!empty( $ansID ) ){

			if($ansID == $this->oAnswer->getResourceId()){
				$err = 'This answer is already a selected answer';

				/**
				 * No point in proceeding any further
				 * if the same answer is already marked
				 * as selected
				 */
				throw new \StudyBuddy\Exception($err);
			}

			/**
			 * Need to set 'accepted' to null
			 * in old answer because it is not longer an accepted one
			 * and also decrease reputation for user who
			 * owned formerly accepted answer
			 */
			$this->getOldAnswer()
			->updateOldAnswer()
			->updateOldAnswerer();

		} else {
			$this->rewardViewer();
		}

		$this->oQuestion->setBestAnswer($this->oAnswer);

		return $this;
	}


	/**
	 * Get array for old answer
	 *
	 * @return object $this
	 */
	protected function getOldAnswer(){
		$this->aOldAnswer = $this->oRegistry->Mongo->ANSWERS->findOne(array('_id' => $this->oQuestion['i_sel_ans']));
		d('old answer: '.print_r($this->aOldAnswer, 1));

		return $this;
	}


	/**
	 * Mark old answer as not accepted
	 *
	 * @return object $this
	 */
	protected function updateOldAnswer(){
		if(!empty($this->aOldAnswer)){
			$this->aOldAnswer['accepted'] = false;
			$this->oRegistry->Mongo->ANSWERS->save($this->aOldAnswer);
		}

		return $this;
	}


	/**
	 * Decrease reputation of user who
	 * owns the old answer
	 *
	 * @todo check current score and make sure
	 * it will not become negative after we deduct
	 * some points
	 *
	 * @return object $this
	 */
	protected function updateOldAnswerer(){
		if(!empty($this->aOldAnswer)){
			$uid = $this->aOldAnswer['i_uid'];
			if(!empty($uid)){
				try{
					\StudyBuddy\User::factory($this->oRegistry)->by_id($uid)->setReputation((0 - \StudyBuddy\Points::BEST_ANSWER))->save();

				} catch(\MongoException $e ){
					e('unable to update reputation for old answerer '.$e->getMessage());
				}
			}
		}

		return $this;
	}


	/**
	 * Increase reputation of user
	 * who answered this question
	 *
	 * But NOT if answered own question
	 *
	 *
	 * @return object $this
	 */
	protected function updateUser(){
		$uid = $this->oAnswer->getOwnerId();
		d('$this->oAnswer->getOwnerId():. '.$uid);

		if(!empty($uid) && ($this->oQuestion['i_uid'] == $uid) ){
			d('Answered own question, this does not count');

			return $this;
		}

		try{
			$this->oRegistry->Mongo->USERS->update(array('_id' => $uid), array('$inc' => array("i_rep" => \StudyBuddy\Points::BEST_ANSWER)));
		} catch(\MongoException $e ){
			e('unable to increase reputation for answerer '.$e->getMessage());
		}

		return $this;
	}
	
	//hoangto make action count
	function makeActionCount($countType = ''){
	
		$coll = $this->oRegistry->Mongo->ACTIONCOUNT;
	
		$count = $coll->findOne(array('i_uid' => $this->oAnswer->getOwnerId()), array('_id',$countType));
		
		if(empty($count[$countType])){
			$total = 1;
		}else{
			$total = (int)$count[$countType] + 1;
		}
			
		if(empty($count['_id'])){
			$countData = array(
				'i_uid' => $this->oAnswer->getOwnerId(),
				$countType => $total
			);
			$this->oRegistry->Mongo->ACTIONCOUNT->insert($countData);
		}else{
			$countData = array(
				'$set' => array($countType => $total)
			);
			$this->oRegistry->Mongo->ACTIONCOUNT->update(array('i_uid' => $this->oAnswer->getOwnerId()), $countData);
		}
		
		$coll->ensureIndex(array('i_uid' => 1));
		
		$this->checkGainBadges($countType, $this->oAnswer->getOwnerId());
		
		return $this;
	}
	
	//hoangto
	protected function checkGainBadges($countType, $uid){
		
		$count = $this->oRegistry->Mongo->ACTIONCOUNT->findOne(array('i_uid' => $uid), array($countType));
		$cond = $this->oRegistry->Mongo->BADGES->find(array('cond_type' => $countType, 'cond' => array('$lte' => $count[$countType])));
		
		foreach($cond as $obj){
			$actData = array(
				'i_uid' => $uid,
				'username' => $this->oAnswer->getUsername(),
				'i_bid' => $obj['id'],
				'badges' => $obj['b_name'],
				'badges_type' => $obj['type']
			);
			$isExist = $this->oRegistry->Mongo->USER_BADGES->findOne($actData);
			if(empty($isExist)){
				$this->oRegistry->Mongo->USER_BADGES->insert($actData);
				$this->makeActivities('badges', 'earned a badges', $obj['id']);
			}
			$this->oRegistry->Mongo->USER_BADGES->ensureIndex(array('i_uid' => 1));
			$this->oRegistry->Mongo->USER_BADGES->ensureIndex(array('badges' => 1));
		}
		
		return $this;
	}
	
	//hoangto make answer activity
	protected function makeActivities($act_type = 'a', $act_name = 'answered a question', $act_id) {
		$actData = array(
			'i_uid' => $this->oAnswer->getOwnerId(),
			'username' => $this->oAnswer->getUsername(),
			'avt' => '',
			'act_type' => $act_type,
			'act_id' => $act_id,
			'act_name' => $act_name,
			'i_ts' => time(),
			'hts' => date('F j, Y g:i a T')
		);
	
		$coll = $this->oRegistry->Mongo->ACTIVITIES;
		$coll->ensureIndex(array('i_uid' => 1));
		$coll->ensureIndex(array('i_ts' => 1));
		
		$oActivity = \StudyBuddy\MongoDoc::factory($this->oRegistry, 'ACTIVITIES', $actData);
		$res = $oActivity->save($actData);
		
		return $this;
	}


	/**
	 * Increase the reputation of Viewer for
	 * accepting an answer BUT
	 * ONLY if this is the first type Viewer
	 * accepted answer for this question
	 *
	 * @return object $this;
	 */
	protected function rewardViewer(){

		/**
		 * Check that accepted by owner of question
		 * In case it was accepted by a moderator
		 * we don't reward moderator by accpeting
		 * the answer for someone else's question
		 */
		if($this->oQuestion->getOwnerId() == $this->oRegistry->Viewer->getUid()){
			$this->oRegistry->Viewer->setReputation(\StudyBuddy\Points::ACCEPT_ANSWER)->save();
		}

		return $this;
	}

	/**
	 * Insert record into VOTE_HACKS collection
	 *
	 * @todo move this to external class and make
	 * this method static, accepting only Registry
	 */
	protected function recordVoteHack(){
		$coll = $this->oRegistry->Mongo->VOTE_HACKS;
		$coll->ensureIndex(array('i_ts' => 1));
		$aData = array(
		'i_uid' => $this->oRegistry->Viewer->getUid(),
		'i_ts' => time(),
		'ip' => Request::getIP());

		$coll->save($aData);

		return $this;
	}


	/**
	 * Redirect user back to url
	 * of the question.
	 *
	 * This time user will see the just selected
	 * answer as 'selected'
	 */
	protected function redirect(){

		Responder::redirectToPage($this->oQuestion->getUrl());
	}
}
